iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0

小明的自動化生產線

經過昨天學習 GitHub Runner 的部署流程,今天我們要深入了解 GitHub Actions 的完整 CI/CD 體系。就像現代農場的自動化生產線一樣,從種子進入到產品出貨,每個環節都有自動化的品質控制和流程管理。

CI/CD 完整概念

持續整合 (Continuous Integration)

持續整合 (Continuous Integration)

持續部署 (Continuous Deployment)

持續部署 (Continuous Deployment)

完整的 CI 工作流程

程式碼品質檢查 Pipeline

# .github/workflows/ci.yml
name: Continuous Integration

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

env:
  PYTHON_VERSION: '3.9'

jobs:
  # 程式碼品質檢查
  lint:
    name: Code Quality Checks
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: ${{ env.PYTHON_VERSION }}
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8 black isort mypy bandit safety
        pip install -r requirements.txt
    
    - name: Code formatting check (Black)
      run: black --check --diff src/
    
    - name: Import sorting check (isort)
      run: isort --check-only --diff src/
    
    - name: Linting (flake8)
      run: flake8 src/ --statistics
    
    - name: Type checking (mypy)
      run: mypy src/ --ignore-missing-imports
    
    - name: Security check (Bandit)
      run: bandit -r src/ -f json -o bandit-report.json
    
    - name: Dependency vulnerability check
      run: safety check --json --output safety-report.json
    
    - name: Upload security reports
      uses: actions/upload-artifact@v3
      if: always()
      with:
        name: security-reports
        path: |
          bandit-report.json
          safety-report.json

  # 單元測試
  unit-tests:
    name: Unit Tests
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.8', '3.9', '3.10']
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pytest pytest-cov pytest-xdist pytest-mock
        pip install -r requirements.txt
        pip install -r requirements-test.txt
    
    - name: Run unit tests
      run: |
        pytest src/tests/unit/ \
          --cov=src/ \
          --cov-report=xml \
          --cov-report=html \
          --junitxml=test-results.xml \
          -v \
          --tb=short
    
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.xml
        flags: unittests
        name: codecov-${{ matrix.python-version }}
    
    - name: Upload test results
      uses: actions/upload-artifact@v3
      if: always()
      with:
        name: test-results-${{ matrix.python-version }}
        path: |
          test-results.xml
          htmlcov/

  # 整合測試
  integration-tests:
    name: Integration Tests
    runs-on: ubuntu-latest
    needs: [lint, unit-tests]
    
    services:
      redis:
        image: redis:6.2
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379
      
      postgres:
        image: postgres:13
        env:
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: trading_test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: ${{ env.PYTHON_VERSION }}
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install -r requirements-test.txt
    
    - name: Set up test environment
      env:
        DATABASE_URL: postgresql://postgres:testpass@localhost:5432/trading_test
        REDIS_URL: redis://localhost:6379
        BYBIT_API_KEY: test_key
        BYBIT_SECRET_KEY: test_secret
      run: |
        # 初始化測試資料庫
        python scripts/init_test_db.py
        
        # 載入測試資料
        python scripts/load_test_data.py
    
    - name: Run integration tests
      env:
        DATABASE_URL: postgresql://postgres:testpass@localhost:5432/trading_test
        REDIS_URL: redis://localhost:6379
      run: |
        pytest src/tests/integration/ \
          --tb=short \
          -v \
          --durations=10

  # Docker 映像建置測試
  docker-build:
    name: Docker Build Test
    runs-on: ubuntu-latest
    needs: [lint, unit-tests]
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2
    
    - name: Build Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        file: ./Dockerfile
        push: false
        tags: trading-bot:test
        cache-from: type=gha
        cache-to: type=gha,mode=max
    
    - name: Test Docker image
      run: |
        # 測試 Docker 映像是否能正常啟動
        docker run --rm -d --name test-container trading-bot:test
        sleep 10
        
        # 檢查容器健康狀態
        if docker ps | grep test-container; then
          echo "✅ Docker 映像測試通過"
          docker stop test-container
        else
          echo "❌ Docker 映像測試失敗"
          docker logs test-container
          exit 1
        fi

進階 CI 功能

條件執行和矩陣策略

# 條件執行範例
jobs:
  deploy-staging:
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    # ... 部署到 staging 環境
  
  deploy-production:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    # ... 部署到 production 環境

  # 矩陣策略測試多種環境
  test-matrix:
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11']
        include:
          - os: ubuntu-latest
            python-version: '3.11'
            experimental: true
        exclude:
          - os: windows-latest
            python-version: '3.8'
    
    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.experimental || false }}

自定義 Actions

# .github/actions/setup-trading-env/action.yml
name: 'Setup Trading Environment'
description: 'Set up Python environment for trading bot'

inputs:
  python-version:
    description: 'Python version to use'
    required: true
    default: '3.9'
  cache-dependency-path:
    description: 'Path to dependency file'
    required: false
    default: 'requirements.txt'

outputs:
  cache-hit:
    description: 'Whether dependencies were restored from cache'
    value: ${{ steps.cache.outputs.cache-hit }}

runs:
  using: 'composite'
  steps:
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: ${{ inputs.python-version }}
    
    - name: Cache dependencies
      id: cache
      uses: actions/cache@v3
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles(inputs.cache-dependency-path) }}
        restore-keys: |
          ${{ runner.os }}-pip-
    
    - name: Install dependencies
      shell: bash
      run: |
        python -m pip install --upgrade pip
        pip install -r ${{ inputs.cache-dependency-path }}

完整的 CD 工作流程

多環境部署策略

# .github/workflows/cd.yml
name: Continuous Deployment

on:
  workflow_run:
    workflows: ["Continuous Integration"]
    branches: [main, develop]
    types: [completed]

jobs:
  # 決定部署目標
  determine-deployment:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    outputs:
      environment: ${{ steps.determine.outputs.environment }}
      should-deploy: ${{ steps.determine.outputs.should-deploy }}
    
    steps:
    - name: Determine deployment environment
      id: determine
      run: |
        if [[ "${{ github.event.workflow_run.head_branch }}" == "main" ]]; then
          echo "environment=production" >> $GITHUB_OUTPUT
          echo "should-deploy=true" >> $GITHUB_OUTPUT
        elif [[ "${{ github.event.workflow_run.head_branch }}" == "develop" ]]; then
          echo "environment=staging" >> $GITHUB_OUTPUT
          echo "should-deploy=true" >> $GITHUB_OUTPUT
        else
          echo "should-deploy=false" >> $GITHUB_OUTPUT
        fi

  # 建置和推送映像
  build-and-push:
    runs-on: ubuntu-latest
    needs: determine-deployment
    if: needs.determine-deployment.outputs.should-deploy == 'true'
    outputs:
      image-uri: ${{ steps.build.outputs.image-uri }}
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-east-1
    
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
    
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: ${{ steps.login-ecr.outputs.registry }}/trading-bot
        tags: |
          type=ref,event=branch
          type=sha,prefix={{branch}}-
          type=raw,value=latest,enable={{is_default_branch}}
    
    - name: Build and push
      id: build
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha
        cache-to: type=gha,mode=max
        build-args: |
          BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
          VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
          REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
    
    - name: Output image URI
      run: |
        IMAGE_URI="${{ steps.login-ecr.outputs.registry }}/trading-bot:${{ github.sha }}"
        echo "image-uri=$IMAGE_URI" >> $GITHUB_OUTPUT

  # 部署到目標環境
  deploy:
    runs-on: ubuntu-latest
    needs: [determine-deployment, build-and-push]
    environment: ${{ needs.determine-deployment.outputs.environment }}
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-east-1
        role-to-assume: ${{ secrets.DEPLOY_ROLE_ARN }}
    
    - name: Deploy to ECS
      uses: ./.github/actions/deploy-ecs
      with:
        cluster-name: trading-${{ needs.determine-deployment.outputs.environment }}
        service-name: trading-bot-service
        image-uri: ${{ needs.build-and-push.outputs.image-uri }}
        task-definition-family: trading-bot-${{ needs.determine-deployment.outputs.environment }}

  # 部署後測試
  post-deployment-tests:
    runs-on: ubuntu-latest
    needs: [determine-deployment, deploy]
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Run smoke tests
      env:
        ENVIRONMENT: ${{ needs.determine-deployment.outputs.environment }}
        API_BASE_URL: ${{ vars.API_BASE_URL }}
      run: |
        python -m pytest tests/smoke/ \
          --base-url=$API_BASE_URL \
          --environment=$ENVIRONMENT \
          -v
    
    - name: Run performance tests
      if: needs.determine-deployment.outputs.environment == 'production'
      run: |
        # 使用 k6 進行效能測試
        k6 run --vus 10 --duration 60s tests/performance/load-test.js

  # 監控和告警設定
  setup-monitoring:
    runs-on: ubuntu-latest
    needs: [determine-deployment, post-deployment-tests]
    if: needs.determine-deployment.outputs.environment == 'production'
    
    steps:
    - name: Update CloudWatch alarms
      run: |
        # 更新 CloudWatch 告警閾值
        aws cloudwatch put-metric-alarm \
          --alarm-name "TradingBot-HighCPU" \
          --alarm-description "High CPU utilization" \
          --metric-name CPUUtilization \
          --namespace AWS/ECS \
          --statistic Average \
          --period 300 \
          --threshold 80 \
          --comparison-operator GreaterThanThreshold \
          --evaluation-periods 2
    
    - name: Update monitoring dashboard
      run: |
        # 更新 Grafana Dashboard
        curl -X POST "${{ secrets.GRAFANA_URL }}/api/dashboards/db" \
             -H "Authorization: Bearer ${{ secrets.GRAFANA_TOKEN }}" \
             -H "Content-Type: application/json" \
             -d @monitoring/dashboard.json

效能最佳化策略

Cache 策略

# 多層次快取策略
- name: Cache node modules
  uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

- name: Cache Python packages
  uses: actions/cache@v3
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

- name: Cache Docker layers
  uses: actions/cache@v3
  with:
    path: /tmp/.buildx-cache
    key: ${{ runner.os }}-buildx-${{ github.sha }}
    restore-keys: |
      ${{ runner.os }}-buildx-

並行化執行

# 並行執行不相依的工作
jobs:
  lint-python:
    runs-on: ubuntu-latest
    # ... Python linting
  
  lint-javascript:
    runs-on: ubuntu-latest
    # ... JavaScript linting
  
  test-unit:
    runs-on: ubuntu-latest
    # ... Unit tests
  
  test-integration:
    runs-on: ubuntu-latest
    needs: [lint-python, lint-javascript]
    # ... Integration tests
  
  build-docker:
    runs-on: ubuntu-latest
    needs: [test-unit]
    # ... Docker build

安全和合規

Secret 掃描

# .github/workflows/security.yml
name: Security Scan

on:
  push:
  pull_request:
  schedule:
    - cron: '0 2 * * *'  # 每日凌晨 2 點執行

jobs:
  secret-scan:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
      with:
        fetch-depth: 0
    
    - name: Run TruffleHog
      uses: trufflesecurity/trufflehog@main
      with:
        path: ./
        base: main
        head: HEAD
    
    - name: Run GitLeaks
      uses: zricethezav/gitleaks-action@master

  dependency-scan:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Run Snyk
      uses: snyk/actions/python@master
      env:
        SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
    
    - name: Upload SARIF file
      uses: github/codeql-action/upload-sarif@v2
      with:
        sarif_file: snyk.sarif

合規性檢查

# 確保部署符合合規要求
compliance-check:
  runs-on: ubuntu-latest
  steps:
  - name: Check deployment window
    run: |
      # 檢查是否在允許的部署時間窗口
      CURRENT_HOUR=$(date +%H)
      CURRENT_DAY=$(date +%u)
      
      # 週一到週五,早上 9 點到下午 5 點允許部署
      if [ $CURRENT_DAY -ge 1 ] && [ $CURRENT_DAY -le 5 ] && 
         [ $CURRENT_HOUR -ge 9 ] && [ $CURRENT_HOUR -le 17 ]; then
        echo "✅ 在允許的部署時間窗口內"
      else
        echo "❌ 超出允許的部署時間窗口"
        exit 1
      fi
  
  - name: Verify approval
    if: github.ref == 'refs/heads/main'
    run: |
      # 檢查是否有必要的審批
      APPROVALS=$(gh pr view ${{ github.event.number }} --json reviews --jq '.reviews | length')
      if [ $APPROVALS -lt 2 ]; then
        echo "❌ 需要至少 2 個審批"
        exit 1
      fi

小結

今天我們深入學習了 GitHub Actions 的完整 CI/CD 體系,就像建立了一個現代化的自動化生產線。重要概念包括:

  1. 持續整合:自動化的程式碼品質檢查和測試
  2. 持續部署:從程式碼到生產環境的自動化流程
  3. 多環境策略:不同環境的差異化部署
  4. 效能最佳化:透過快取和並行化提升執行效率
  5. 安全合規:內建的安全檢查和合規性驗證
  6. 監控整合:部署後的自動化監控設定

明天我們將學習 GitHub Environment 的設定,了解如何管理不同環境的配置和權限!


下一篇:Day 14 - Github Setup Environment


上一篇
Day 12: Github Runner - Deploy Prod
系列文
小資族的量化交易 10113
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言